package Encrypter;


import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;


/**
 * Permite el manejo de encriptaci&oacute;n y desencriptaci&oacute;n de objetos <code>String</code>.
 *
 * @version 1.0
  */
public class StringEncrypter {
    /**
     * Esquema utilizado para la encriptaci&oacute;n.
     */
    private static final String DESEDE_ENCRYPTION_SCHEME = "DESede";

    /**
     * Que tipo de caracteres utiliza.
     */
    private static final String UNICODE_FORMAT = "UTF8";

    /**
     * <code>String</code> default que se utilizara como referencia para los caracteres
     * a un utilizar en encriptaci&oacute;n.
     */
    private static final String DEFAULT_ENCRYPTION_KEY =
        "Esta frase sirve como referencia para encriptar";

    /**
     * Clase que se toma para generar un <code>SecretKey</code>.
     */
    private KeySpec keySpec;

    /**
     * Clase que sirve para generar clave de encriptaci&oacute;n.
     */
    private SecretKeyFactory keyFactory;

    /**
     * Clase que realiza encriptaci&oacute;n y desencriptaci&oacute;n.
     */
    private Cipher cipher;

    /**
     * Crea un nuevo objeto de tipo StringEncrypter.
     *
     * @param defaultEncriptionKey <code>String</code> que se utilizara como referencia para los
     *        caracteres a un utilizar en encriptaci&oacute;n.
     */
    public StringEncrypter(String defaultEncriptionKey) {
        if ((defaultEncriptionKey == null) || (defaultEncriptionKey.trim().length() < 24)) {
            throw new IllegalArgumentException(
                "El key para encriptar debe tener al menos 24 caracteres");
        }

        try {
            keyFactory = SecretKeyFactory.getInstance(DESEDE_ENCRYPTION_SCHEME);
            cipher = Cipher.getInstance(DESEDE_ENCRYPTION_SCHEME);
            keySpec = new DESedeKeySpec(defaultEncriptionKey.getBytes(UNICODE_FORMAT));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Crea un nuevo objeto de tipo StringEncrypter.
     */
    public StringEncrypter() {
        this(DEFAULT_ENCRYPTION_KEY);
    }

    /**
     * Devuelve un <code>String</code> encriptado.
     *
     * @param unencryptedString <code>String</code> a ser encriptado.
     *
     * @return <code>String</code>
     *
     * @throws EncryptionException Exception lanzada si ocurre alg&uacute;n error
     */
    public String encrypt(String unencryptedString) throws EncryptionException {
        if ((unencryptedString == null) || (unencryptedString.trim().length() == 0)) {
            throw new IllegalArgumentException("El parametro unencrypted string era null o vacio");
        }

        try {
            SecretKey key = keyFactory.generateSecret(keySpec);
            cipher.init(Cipher.ENCRYPT_MODE, key);

            byte[] clearText = unencryptedString.getBytes(UNICODE_FORMAT);
            byte[] encriptedText = cipher.doFinal(clearText);

            sun.misc.BASE64Encoder base64encoder = new sun.misc.BASE64Encoder();

            return base64encoder.encode(encriptedText);
        } catch (Exception e) {
            throw new EncryptionException(e);
        }
    }

    /**
     * Devuelve un <code>String</code> desencriptado.
     *
     * @param encryptedString <code>String</code> a desencriptar
     *
     * @return <code>String</code>
     *
     * @throws EncryptionException Exception lanzada si ocurre alg&uacute;n error
     */
    public String decrypt(String encryptedString) throws EncryptionException {
        if ((encryptedString == null) || (encryptedString.trim().length() == 0)) {
            throw new IllegalArgumentException("El parametro unencrypted string era null o vacio");
        }

        try {
            SecretKey key = keyFactory.generateSecret(keySpec);
            cipher.init(Cipher.DECRYPT_MODE, key);

            sun.misc.BASE64Decoder base64decoder = new sun.misc.BASE64Decoder();
            byte[] clearText = base64decoder.decodeBuffer(encryptedString);
            byte[] decryptedText = cipher.doFinal(clearText);

            return bytesToString(decryptedText);
        } catch (Exception e) {
            throw new EncryptionException(e);
        }
    }

    /**
     * Convierte un arreglos de tipo <code>byte</code> a un objeto <code>String</code>.
     *
     * @param bytes array de tipo <code>byte</code>
     *
     * @return <code>String</code>
     */
    private static String bytesToString(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();

        for (byte b : bytes) {
            stringBuffer.append((char) b);
        }

        return stringBuffer.toString();
    }

    /**
     * Exception que sirve para ser lanzada cuando ocurre alg&uacute;n de encriptado.
     *
     * @version 1.0
      */
    public static class EncryptionException extends Exception {
        /**
         * Crea un nuevo objeto de tipo EncryptionException.
         *
         * @param t <code>Throwable</code>
         */
        public EncryptionException(Throwable t) {
            super(t);
        }
    }
}
